/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.reflection.wrapper;

import org.apache.ibatis.reflection.*;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.apache.ibatis.reflection.property.PropertyTokenizer;

import java.util.List;

/**
 * javaBean包装器
 *
 * @author Clinton Begin
 */
public class BeanWrapper extends BaseWrapper {
    /**
     * 原始对象
     */
    private final Object object;
    /**
     * 类型元数据
     */
    private final MetaClass metaClass;

    public BeanWrapper(MetaObject metaObject, Object object) {
        super(metaObject);
        // 缓存被包装的原始对象
        this.object = object;
        // 获取原始对象的类型描述元数据
        this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory());
    }

    @Override
    public Object get(PropertyTokenizer prop) {
        if (prop.getIndex() != null) {
            // 如果属性描述符包含索引，表示对应的值是集合，利用BaseWrapper提供的方法解析出对应的集合之
            // 处理集合
            Object collection = resolveCollection(prop, object);
            return getCollectionValue(prop, collection);
        } else {
            // 获取属性值
            return getBeanProperty(prop, object);
        }
    }

    /**
     * 如果属性中包含了[, 将会被保存为集合类型
     *
     * @param prop  属性名称
     * @param value 值
     */
    @Override
    public void set(PropertyTokenizer prop, Object value) {
        // 判断是否包含了索引,例如:users[jpanda]
        if (prop.getIndex() != null) {
            // 获取集合对象
            Object collection = resolveCollection(prop, object);
            // 设值
            setCollectionValue(prop, collection, value);
        } else {
            // 为对象设值
            setBeanProperty(prop, object, value);
        }
    }

    @Override
    public String findProperty(String name, boolean useCamelCaseMapping) {
        return metaClass.findProperty(name, useCamelCaseMapping);
    }

    @Override
    public String[] getGetterNames() {
        return metaClass.getGetterNames();
    }

    @Override
    public String[] getSetterNames() {
        return metaClass.getSetterNames();
    }

    /**
     * 获取指定属性对应的set方法的值类型
     *
     * @param name 属性名称
     * @return 参数类型
     */
    @Override
    public Class<?> getSetterType(String name) {
        // 迭代化属性
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            // 获取该属性对应的对象元数据
            MetaObject metaValue = metaObject.metaObjectForProperty(prop.getIndexedName());

            if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
                //  表示该属性的值为null，通过该属性的类型元数据来获取
                return metaClass.getSetterType(name);
            } else {
                // 如果找到了对象元数据，则继续解析属性值，直到属性表达式完全被解析完成
                return metaValue.getSetterType(prop.getChildren());
            }
        } else {
            // 如果是简单属性描述符，通过属性的类型元数据获取具体的属性类型
            return metaClass.getSetterType(name);
        }
    }

    @Override
    public Class<?> getGetterType(String name) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            MetaObject metaValue = metaObject.metaObjectForProperty(prop.getIndexedName());
            if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
                return metaClass.getGetterType(name);
            } else {
                return metaValue.getGetterType(prop.getChildren());
            }
        } else {
            return metaClass.getGetterType(name);
        }
    }

    @Override
    public boolean hasSetter(String name) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            if (metaClass.hasSetter(prop.getIndexedName())) {
                MetaObject metaValue = metaObject.metaObjectForProperty(prop.getIndexedName());
                if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
                    return metaClass.hasSetter(name);
                } else {
                    return metaValue.hasSetter(prop.getChildren());
                }
            } else {
                return false;
            }
        } else {
            return metaClass.hasSetter(name);
        }
    }

    /**
     * 判断在bean包装器中是否包含指定属性的getter方法
     *
     * @param name 指定属性名称
     */
    @Override
    public boolean hasGetter(String name) {
        // 迭代化属性名称
        PropertyTokenizer prop = new PropertyTokenizer(name);
        // 递归
        if (prop.hasNext()) {
            if (metaClass.hasGetter(prop.getIndexedName())) {
                MetaObject metaValue = metaObject.metaObjectForProperty(prop.getIndexedName());
                if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
                    return metaClass.hasGetter(name);
                } else {
                    return metaValue.hasGetter(prop.getChildren());
                }
            } else {
                return false;
            }
        } else {
            return metaClass.hasGetter(name);
        }
    }

    /**
     * 实例化属性值
     *
     * @param name          字段名称
     * @param prop          属性
     * @param objectFactory 对象工厂
     * @return 实例化后的该对象的对象元数据
     */
    @Override
    public MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory) {
        // 被实例化的对象的对象元数据
        MetaObject metaValue;
        // 获取属性对应setter方法的入参类型名称
        Class<?> type = getSetterType(prop.getName());
        try {
            // 创建指定类型的实例对象
            Object newObject = objectFactory.create(type);
            // 获取新生成对象的对象元数据
            metaValue = MetaObject.forObject(newObject, metaObject.getObjectFactory(), metaObject.getObjectWrapperFactory(), metaObject.getReflectorFactory());
            // 保存属性和对象之间的关系
            set(prop, newObject);
        } catch (Exception e) {
            throw new ReflectionException("Cannot set value of property '" + name + "' because '" + name + "' is null and cannot be instantiated on instance of " + type.getName() + ". Cause:" + e.toString(), e);
        }
        return metaValue;
    }

    private Object getBeanProperty(PropertyTokenizer prop, Object object) {
        try {
            // 获取该属性对应的get方法反射操作执行器
            Invoker method = metaClass.getGetInvoker(prop.getName());
            try {
                // 执行反射操作，获取数据
                return method.invoke(object, NO_ARGUMENTS);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (Throwable t) {
            throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ".  Cause: " + t.toString(), t);
        }
    }

    private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
        try {
            // 获取该属性对应的set方法反射操作执行器
            Invoker method = metaClass.getSetInvoker(prop.getName());
            // 初始化参数
            Object[] params = {value};
            try {
                // 执行反射操作
                method.invoke(object, params);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        } catch (Throwable t) {
            throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
        }
    }

    @Override
    public boolean isCollection() {
        return false;
    }

    @Override
    public void add(Object element) {
        throw new UnsupportedOperationException();
    }

    @Override
    public <E> void addAll(List<E> list) {
        throw new UnsupportedOperationException();
    }

}
